// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#pragma once

namespace Weathr
{
    // Runs a function in the main thread with non-interactive priority.
    void run_async_non_interactive(std::function<void ()>&& action);

    // Observes all exceptions that occurred in all tasks in the given range.
    template<class T, class InIt>
    void observe_all_exceptions(InIt first, InIt last)
    {
        std::for_each(first, last, [](task<T> t)
        {
            t.then([](concurrency::task<T> previousTask)
            {
                try
                {
                    previousTask.get();
                }
                catch (Platform::Exception^)
                {
                    // Swallow the exception.
                }
                catch (const std::exception&)
                {
                    // Swallow the exception.
                }
            });
        });
    }

    // From http://blogs.msdn.com/b/nativeconcurrency/archive/2012/03/26/ppl-asynchronous-sample-pack-updated.aspx
    // See also http://msdn.microsoft.com/en-us/library/hh873170.aspx for a second example.
    template <typename Func>
    auto create_delayed_task(std::chrono::milliseconds delay, Func func, concurrency::cancellation_token token = concurrency::cancellation_token::none())->decltype(create_task(func))
    {
        concurrency::task_completion_event<void> tce;

        auto pTimer = new concurrency::timer<int>(static_cast<int>(delay.count()), 0, nullptr, false);
        auto pCallback = new concurrency::call<int>([tce](int) {
            tce.set();
        });

        pTimer->link_target(pCallback);
        pTimer->start();

        return create_task(tce).then([pCallback, pTimer]() {
            delete pCallback;
            delete pTimer;
        }).then(func, token);
    }

    // Performs the given operation, and calls the user-supplied predicate if the action fails.
    // The predicate takes the retry count and returns a task that produces whether to retry the operation.
    template<typename T>
    inline concurrency::task<T> run_async_with_retry(const std::function<concurrency::task<T> ()>& asyncOperation, const std::function<concurrency::task<bool> (uint32_t)>& asyncContinuePredicate, uint32_t retryCount = 0)
    {
        return asyncOperation().then([asyncOperation, asyncContinuePredicate, retryCount](concurrency::task<T> previousTask)
        {
            try
            {
                // Raise any exception that occurred during the task.
                previousTask.get();

                // No exception occurred; return the previous task.
                return previousTask;
            }
            catch (const concurrency::task_canceled&)
            {
                // The previous task was cancelled. Don't retry it.
                concurrency::cancel_current_task();
            }
            catch (...)
            {
                // Some other unhandled exception occurred. Run the continue predicate.
                return asyncContinuePredicate(retryCount).then([asyncOperation, asyncContinuePredicate, retryCount, previousTask](bool result)
                {
                    if (result)
                    {
                        // Retry the operation.
                        return run_async_with_retry(asyncOperation, asyncContinuePredicate, retryCount + 1);
                    }
                    else
                    {
                        // Rethrow the original exception.
                        previousTask.get();
                        __assume(0); // Will never be reached because the previous get() call throws.
                    }
                    // Use current context so that the operation is always called from the same context.
                }, concurrency::task_continuation_context::use_current());
            }
            // Use current context so that the operation is always called from the same context.
        }, concurrency::task_continuation_context::use_current());
    }
}

#ifndef NDEBUG

#ifdef  __cplusplus
extern "C" {
#endif

    // Called at startup to remember the main thread id for debug assertion checking
    void RecordMainThread(void);

    // Called in debug mode only for thread context assertion checks
    bool IsMainThread(void);

    // Called in debug mode only for thread context assertion checks
    bool IsBackgroundThread(void);

#ifdef  __cplusplus
}
#endif

#endif /* not NDEBUG */